package pl.mchaniewski.ryneczek.services.impl;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import pl.mchaniewski.ryneczek.dao.CompanyDao;
import pl.mchaniewski.ryneczek.dao.OrderDao;
import pl.mchaniewski.ryneczek.dao.PortfolioDao;
import pl.mchaniewski.ryneczek.dao.QuotationDao;
import pl.mchaniewski.ryneczek.pojo.Company;
import pl.mchaniewski.ryneczek.pojo.Order;
import pl.mchaniewski.ryneczek.pojo.Portfolio;
import pl.mchaniewski.ryneczek.pojo.Quotation;
import pl.mchaniewski.ryneczek.pojo.User;
import pl.mchaniewski.ryneczek.services.PortfolioService;
import pl.mchaniewski.ryneczek.utilities.DateUtil;

@Service("portfolioService")
public class PortfolioServiceImpl implements PortfolioService {
    private static final Logger logger = LoggerFactory
            .getLogger(PortfolioServiceImpl.class);
    private PortfolioDao portfolioDao;
    private OrderDao orderDao;
    private CompanyDao companyDao;
    private QuotationDao quotationDao;

    private static String DEFAULT_TOTAL_COMMISSIONS = "0";

    @Autowired
    public void setPortfolioDao(PortfolioDao portfolioDao) {
        this.portfolioDao = portfolioDao;
    }

    @Autowired
    public void setOrderDao(OrderDao orderDao) {
        this.orderDao = orderDao;
    }

    @Autowired
    public void setCompanyDao(CompanyDao companyDao) {
        this.companyDao = companyDao;
    }

    @Autowired
    public void setQuotationDao(QuotationDao quotationDao) {
        this.quotationDao = quotationDao;
    }

    @Override
    public Portfolio save(Portfolio entity) {
        try {
            return portfolioDao.save(entity);
        } catch (Exception ex) {
            logger.error("Exception: {}", ex.getMessage());

            return null;
        }
    }

    @Override
    public Portfolio findById(Integer portfolioid) {
        try {
            return portfolioDao.findById(portfolioid);
        } catch (Exception ex) {
            logger.error("Exception: {}", ex.getMessage());

            return null;
        }
    }

    @Override
    public List<Portfolio> findByUserId(Integer userid) {
        try {
            return portfolioDao.findByUserId(userid);
        } catch (Exception ex) {
            logger.error("Exception: {}", ex.getMessage());

            return null;
        }
    }

    @Override
    public List<Portfolio> findByUsername(String name) {
        try {
            return portfolioDao.findByUsername(name);
        } catch (Exception ex) {
            logger.error("Exception: {}", ex.getMessage());

            return null;
        }
    }

    @Override
    public Portfolio createNewPortfolio(User user, String name,
            BigDecimal balance) {
        Portfolio newPortfolio = new Portfolio();
        newPortfolio.setUser(user);
        newPortfolio.setName(name);
        newPortfolio.setOriginalBalance(balance);
        newPortfolio.setBalance(balance);
        newPortfolio.setCreateDate(DateUtil.getNow());
        newPortfolio.setTotalCommissions(new BigDecimal(
                DEFAULT_TOTAL_COMMISSIONS));

        return save(newPortfolio);
    }

    @Override
    public Portfolio changeName(Integer portfolioid, String newName) {
        Portfolio portf;
        try {
            portf = portfolioDao.findById(portfolioid);
        } catch (Exception ex) {
            logger.error("Exception: {}", ex.getMessage());

            return null;
        }

        portf.setName(newName);

        return save(portf);
    }

    @Override
    public Integer getPortfolioCompanySharesAmount(Integer portfolioid,
            Integer comapanyid) {
        Integer count = 0;
        List<Order> orders;

        try {
            orders = orderDao.findByStatusPortfolioIdAndCompanyId(portfolioid,
                    comapanyid, Order.STATUS_COMPLETED);
        } catch (Exception ex) {
            logger.error("Exception: {}", ex.getMessage());

            return null;
        }

        for (Order o : orders) {
            if (o.getTransactionType().equals(Order.TRANSACTION_TYPE_BUY)) {
                count += o.getAmount();
            } else {
                count -= o.getAmount();
            }
        }

        if (count < 0) {
            logger.warn(
                    "Portfolio with ID:{} has incorrect number of company shares with ID:{}",
                    portfolioid, comapanyid);

            return null;
        }

        return count;
    }

    @Override
    public BigDecimal getPortfolioBlockedBalance(Integer portfolioid) {
        List<Order> activeOrders;

        try {
            activeOrders = orderDao.findByPortfolioIdStatusAndTransactionType(
                    portfolioid, Order.STATUS_ACCEPTED,
                    Order.TRANSACTION_TYPE_BUY);
        } catch (Exception ex) {
            logger.error("Exception: {}", ex.getMessage());

            return null;
        }

        if (activeOrders == null || activeOrders.size() == 0) {
            return BigDecimal.ZERO;
        } else {
            BigDecimal sum = BigDecimal.ZERO;
            for (Order o : activeOrders) {
                sum = sum.add(Order.getTotalTransactionValue(o.getAmount(),
                        o.getOriginalPrice()));
            }

            return sum;
        }
    }

    @Override
    public BigDecimal getPortfolioRealBalance(Integer portfolioid) {
        Portfolio portfolio;
        try {
            portfolio = portfolioDao.findById(portfolioid);
        } catch (Exception ex) {
            logger.error("Exception: {}", ex.getMessage());

            return null;
        }

        BigDecimal balance = portfolio.getBalance();
        BigDecimal blockedBalance = getPortfolioBlockedBalance(portfolioid);

        if (blockedBalance == null) {
            return null;
        }

        return balance.subtract(blockedBalance);
    }

    @Override
    public List<Company> getPortfolioCompanies(Integer portfolioid) {
        List<Company> companies;
        try {
            companies = companyDao.findDistinctInPortfolio(portfolioid);
        } catch (Exception ex) {
            logger.error("Exception: {}", ex.getMessage());

            return null;
        }

        return filterOutCompaniesWithoutShares(portfolioid, companies);
    }

    @Override
    public List<Company> getPortfolioCompanies(Integer portfolioid,
            boolean wasInLastUpdate) {
        List<Company> companies;
        try {
            companies = companyDao.findDistinctInPortfolio(portfolioid,
                    wasInLastUpdate);
        } catch (Exception ex) {
            logger.error("Exception: {}", ex.getMessage());

            return null;
        }

        return filterOutCompaniesWithoutShares(portfolioid, companies);
    }

    private List<Company> filterOutCompaniesWithoutShares(Integer portfolioid,
            List<Company> companies) {
        List<Company> temp = new LinkedList<Company>();
        for (Company c : companies) {
            Integer count = getPortfolioCompanySharesAmount(portfolioid,
                    c.getCompanyid());
            if (count != null && count > 0) {
                temp.add(c);
            }
        }

        return temp;
    }

    @Override
    public Map<String, Object> getPortfolioCompanyInfo(Integer portfolioid,
            Company company) {
        Integer amount = getPortfolioCompanySharesAmount(portfolioid,
                company.getCompanyid());

        if (amount == null || amount == 0) {
            return null;
        }

        List<Quotation> latestQuotes;
        try {
            latestQuotes = quotationDao.findLatest(company.getCompanyid());
        } catch (Exception ex) {
            logger.error("Exception: {}", ex.getMessage());

            return null;
        }

        if (latestQuotes == null || latestQuotes.size() == 0) {
            return null;
        }

        BigDecimal currentValue = latestQuotes.get(0).getCurrentValue();
        BigDecimal totalValue = currentValue.multiply(BigDecimal
                .valueOf(amount));
        BigDecimal spentOnShares = getValueSpentOnShares(portfolioid,
                company.getCompanyid());

        BigDecimal difference = null;
        if (spentOnShares != null
                && spentOnShares.compareTo(BigDecimal.ZERO) != 0) {
            difference = new BigDecimal("100").multiply(totalValue).divide(
                    spentOnShares, 2, RoundingMode.HALF_DOWN);
        }

        Float floatDifference = null;
        if (difference != null) {
            floatDifference = difference.subtract(new BigDecimal("100"))
                    .floatValue();
        }

        Integer completedAmount = getOrdersAmount(portfolioid,
                company.getCompanyid(), Order.STATUS_COMPLETED);
        Integer canceledAmount = getOrdersAmount(portfolioid,
                company.getCompanyid(), Order.STATUS_CANCELED);

        Map<String, Object> shares = new LinkedHashMap<String, Object>();
        shares.put("name", company.getName());
        shares.put("amount", amount.intValue());
        shares.put("value", totalValue);
        shares.put("spentOnShares", spentOnShares);
        shares.put("difference", floatDifference);
        shares.put("active", company.isWasInLastUpdate() ? "YES" : "NO");
        shares.put("completedAmount", completedAmount);
        shares.put("canceledAmount", canceledAmount);
        shares.put("lastQuotationDate",
                DateUtil.dateToString(latestQuotes.get(0).getListingDate()));

        return shares;
    }

    private BigDecimal getValueSpentOnShares(Integer portfolioid,
            Integer companyid) {
        List<Order> orders;

        try {
            orders = orderDao.findByStatusPortfolioIdAndCompanyId(portfolioid,
                    companyid, Order.STATUS_COMPLETED);
        } catch (Exception ex) {
            logger.error("Exception: {}", ex.getMessage());

            return null;
        }

        BigDecimal totalValue = null;
        if (orders != null) {
            totalValue = BigDecimal.ZERO;

            for (Order o : orders) {
                if (o.getTransactionType().equals(Order.TRANSACTION_TYPE_BUY)) {
                    totalValue = totalValue.add(o.getTransactionValue());
                } else {
                    totalValue = totalValue.subtract(o.getTransactionValue());
                }

                totalValue = totalValue.add(o.getCommissionValue());
            }
        }

        return totalValue;
    }

    private Integer getOrdersAmount(Integer portfolioid, Integer companyid,
            String status) {
        List<Order> orders;

        try {
            orders = orderDao.findByStatusPortfolioIdAndCompanyId(portfolioid,
                    companyid, status);
        } catch (Exception ex) {
            logger.error("Exception: {}", ex.getMessage());

            return null;
        }

        return orders.size();
    }

    @Override
    public List<Map<String, Object>> getPortfolioCompaniesInfo(
            Integer portfolioid) {
        List<Company> companiesInPortfolio = getPortfolioCompanies(portfolioid);
        if (companiesInPortfolio == null || companiesInPortfolio.size() == 0) {
            return null;
        }

        List<Map<String, Object>> sharesInfo = new LinkedList<Map<String, Object>>();
        for (Company c : companiesInPortfolio) {
            Map<String, Object> companyInfo = getPortfolioCompanyInfo(
                    portfolioid, c);

            if (companyInfo != null) {
                sharesInfo.add(companyInfo);
            }
        }

        return sharesInfo;
    }

    @Override
    public Map<String, BigDecimal> getPortfolioAllStockValues(
            Integer portfolioid) {
        List<Company> companiesInPortfolio = getPortfolioCompanies(portfolioid);

        return getPortfolioStockValues(portfolioid, companiesInPortfolio);
    }

    @Override
    public Map<String, BigDecimal> getPortfolioActiveStockValues(
            Integer portfolioid) {
        List<Company> companiesInPortfolio = getPortfolioCompanies(portfolioid,
                true);

        return getPortfolioStockValues(portfolioid, companiesInPortfolio);
    }

    private Map<String, BigDecimal> getPortfolioStockValues(
            Integer portfolioid, List<Company> companiesInPortfolio) {
        if (companiesInPortfolio == null || companiesInPortfolio.size() == 0) {
            return null;
        }

        Map<String, BigDecimal> map = new LinkedHashMap<String, BigDecimal>();
        for (Company comp : companiesInPortfolio) {
            Integer amount = getPortfolioCompanySharesAmount(portfolioid,
                    comp.getCompanyid());

            if (amount == null || amount == 0) {
                continue;
            }

            List<Quotation> latestQuotes;
            try {
                latestQuotes = quotationDao.findLatest(comp.getCompanyid(),
                        true);
            } catch (Exception ex) {
                logger.error("Exception: {}", ex.getMessage());

                return null;
            }

            if (latestQuotes == null || latestQuotes.size() == 0) {
                continue;
            }

            BigDecimal currentValue = latestQuotes.get(0).getCurrentValue();
            BigDecimal totalValue = currentValue.multiply(BigDecimal
                    .valueOf(amount));

            map.put(comp.getName(), totalValue);
        }

        return map;

    }

}
